#pragma once

#ifdef __cplusplus
#   include <boost/format.hpp>
#endif
#include "compat.h"

#define LOG(level, msg) \
    cxutil_log(level, __FILE__, __LINE__, __func__, msg)

#define LOGF(level, fmt, ...) \
    cxutil_logf(level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__)

enum LogLevel {
    None,
    Debug,
    Info,
    Warning,
    Error,
    Critical,
};

LINKAGE("C") enum LogLevel g_cxutil_logging_threshold;

LINKAGE("C")
void cxutil_log(
    enum LogLevel level,
    char const *file,
    int line,
    char const *func,
    char const *msg);

LINKAGE("C")
FUNCATTR_FORMAT(printf, 5, 6)
void cxutil_logf(
    enum LogLevel level,
    char const *file,
    int line,
    char const *func,
    char const *fmt,
    ...);

/* SOME compilers lack sensible checks for format strings despite the fact that
their respective companies have supported the language and built their operating
system on it for 27 years, so some arcane C++ follows to prevent bad behaviour */
#ifdef __cplusplus
    // this function can be removed when all compilers support variadic templates
    inline void cxutil_logf(
        enum LogLevel level,
        char const *file,
        int line,
        char const *func,
        boost::format &fmt)
    {
        cxutil_log(level, file, line, func, str(fmt).c_str());
    }
#   if      !defined(_MSC_VER) && \
            (!defined(__GNUC__) || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 3))
        inline void cxutil_logf(boost::format &)
        {
        }
        template <typename Value, typename... Args>
        inline void
        cxutil_logf(boost::format &format, Value const &value, Args... args)
        {
            cxutil_logf(format % value, args...);
        }
        template <typename... Args>
        void cxutil_logf(
            enum LogLevel level,
            char const *file,
            int line,
            char const *func,
            char const *fmtstr,
            Args... args)
        {
            if (level < g_cxutil_logging_threshold)
                return;
            boost::format format(fmtstr);
            cxutil_logf(format, args...);
            cxutil_log(level, file, line, func, str(format).c_str());
        }
    #endif
#endif
